/*
 * CCVisu is a tool for visual graph clustering
 * and general force-directed graph layout.
 * This file is part of CCVisu.
 *
 * Copyright (C) 2005-2012  Dirk Beyer
 *
 * CCVisu is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * CCVisu is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with CCVisu; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Please find the GNU Lesser General Public License in file
 * license_lgpl.txt or http://www.gnu.org/licenses/lgpl.txt
 *
 * Dirk Beyer    (firstname.lastname@uni-passau.de)
 * University of Passau, Bavaria, Germany
 */
package org.sosy_lab.ccvisu.ui;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.Arrays;

import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JInternalFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;
import javax.swing.filechooser.FileFilter;

import org.sosy_lab.ccvisu.CCVisuController;
import org.sosy_lab.ccvisu.Options;
import org.sosy_lab.ccvisu.Options.InFormat;
import org.sosy_lab.ccvisu.Options.OptionsEnum;
import org.sosy_lab.ccvisu.Options.OutFormat;
import org.sosy_lab.ccvisu.readers.ReaderData;
import org.sosy_lab.ccvisu.readers.ReaderDataLayoutLAY;
import org.sosy_lab.ccvisu.ui.FrameDisplay.GraphCanvas;
import org.sosy_lab.ccvisu.ui.FrameDisplay.LoadDirection;
import org.sosy_lab.ccvisu.ui.controlpanel.OptionsPanelVisualization;
import org.sosy_lab.ccvisu.writers.Writer;
import org.sosy_lab.ccvisu.writers.WriterDataLayoutDISP;
import org.sosy_lab.ccvisu.writers.WriterDataLayoutLAY;
import org.sosy_lab.ccvisu.writers.WriterDataLayoutSVG;
import org.sosy_lab.ccvisu.writers.WriterDataLayoutVRML;
import org.sosy_lab.util.CCVisuWorkerManager;
import org.sosy_lab.util.interfaces.ProgressReceiver;
import org.sosy_lab.util.interfaces.WorkerManager;

/**
 * The application's main frame.
 */
public class FrameMain extends JFrame {

  private static final long    serialVersionUID  = -4778809967193305287L;

  private JPanel               mainContainer     = new JPanel();

  private WorkerManager        workerManager;

  private final Options        options;
  private JDialog              optionsDialog;
  private PrintWriter          printer;
  private CCVisuController     ccvisuController;

  private FrameTools           propertiesWindow;
  private FrameMenuBar         menuBar;
  private SearchPanel          searchPanel;
  private StatusBar            statusBar;
  private JInternalFrame       graphLayoutFrame;

  private WriterDataLayoutDISP writer;
  private GraphCanvas          canvas;
  private FrameDisplay         display;

  public FrameMain(CCVisuController ccvisuController, Options options,
      JInternalFrame graphLayoutFrame) {

    // set fields defined by parametes
    this.options = options;
    this.graphLayoutFrame = graphLayoutFrame;
    this.ccvisuController = ccvisuController;

    setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    setSize(1280, 768);

    // create printer and writer
    printer = new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out)));
    writer = new WriterDataLayoutDISP(printer, options.graph, options.frame, options);

    workerManager = new CCVisuWorkerManager();

    // set display and canvas
    display = writer.getDisplay();
    canvas = display.getCanvas();

    initComponents();

    workerManager.addReceiver(statusBar);

    updateWindow();
  }

  private void initComponents() {
    searchPanel = new SearchPanel();
    searchPanel.setVisible(false);

    statusBar = new StatusBar();

    mainContainer.setBorder(javax.swing.BorderFactory.createEmptyBorder(4, 4, 4, 4));
    mainContainer.setLayout(new BorderLayout());
    mainContainer.add(searchPanel, BorderLayout.NORTH);
    mainContainer.add(graphLayoutFrame, BorderLayout.CENTER);
    mainContainer.add(statusBar, BorderLayout.SOUTH);

    menuBar = new FrameMenuBar(this, options);

    setLayout(new BorderLayout());
    add(mainContainer, BorderLayout.CENTER);
    setJMenuBar(menuBar);

    propertiesWindow = new FrameTools(this.options, writer, workerManager);
    propertiesWindow.setVisible(true);
    add(propertiesWindow, BorderLayout.EAST);

    menuBar.updateMenuStatus();
  }

  /**
   * Set the window title. Include the name of the active file.
   */
  private void updateWindow() {
    File inputFile = new File(options.getOption(OptionsEnum.inputName).getString());
    setTitle("CCVisu | www.sosy-lab.org | ".concat(inputFile.getName()));

    if (canvas != null) {
      canvas.updateAndPaint();
    }
  }

  public boolean isSearchPanelShown() {
    return searchPanel.isVisible();
  }

  public void setShowSearchPanel(boolean showSearchPanel) {
    searchPanel.setVisible(showSearchPanel);
  }

  public JComponent getCanvasContainer() {
    return mainContainer;
  }

  void resetZoom() {
    canvas.zoomOut();
  }

  void showOrHideControlPanel() {
    propertiesWindow.setVisible(!propertiesWindow.isVisible());
  }

  boolean isControlPanelShown() {
    if (propertiesWindow == null) {
      return false;
    } else {
      return propertiesWindow.isVisible();
    }
  }

  void openSaveLayoutDialog() {
    JFileChooser fileDialog = new JFileChooser(".");
    fileDialog.setFileFilter(ReaderData.mkExtensionFileFilter(OutFormat.LAY));
    int outcome = fileDialog.showSaveDialog(canvas.getParentFrame());

    if (outcome == JFileChooser.APPROVE_OPTION) {
      assert (fileDialog.getCurrentDirectory() != null);
      assert (fileDialog.getSelectedFile() != null);

      String fileName = fileDialog.getCurrentDirectory().toString()
          + File.separator
          + fileDialog.getSelectedFile().getName();

      if (!fileName.endsWith(OutFormat.LAY.getFileExtension())) {
        fileName += OutFormat.LAY.getFileExtension();
      }

      // write file layout
      writeFileLayout(fileName);
    }
  }

  void openLoadLayoutDialog() {
    JFileChooser fileDialog = new JFileChooser(".");
    fileDialog.setFileFilter(ReaderData.mkExtensionFileFilter(InFormat.LAY));
    int outcome = fileDialog.showOpenDialog(canvas.getParentFrame());

    if (outcome == JFileChooser.APPROVE_OPTION) {
      assert (fileDialog.getCurrentDirectory() != null);
      assert (fileDialog.getSelectedFile() != null);

      loadFile(fileDialog.getCurrentDirectory().toString()
          + File.separator + fileDialog.getSelectedFile().getName());
    }
  }

  /**
   * Open a dialog to edit various visual options not supported via the menu.
   */
  void openVisualOptionsDialog() {
    if (this.optionsDialog == null) {
      // create the dialog
      optionsDialog = new JDialog(this, "Options");

      JPanel panel = new JPanel();
      panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
      panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS));
      panel.add(new OptionsPanelVisualization(options, writer));

      JPanel buttonPane = new JPanel();
      buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.LINE_AXIS));
      buttonPane.setBorder(BorderFactory.createEmptyBorder(10, 0, 0, 0));
      buttonPane.add(Box.createHorizontalGlue());

      JButton okButton = new JButton("Ok");
      okButton.setMnemonic('o');
      okButton.addActionListener(new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent e) {
          optionsDialog.setVisible(false);
        }
      });

      buttonPane.add(okButton);
      buttonPane.add(Box.createRigidArea(new Dimension(10, 0)));
      panel.add(buttonPane);

      optionsDialog.add(panel);
      optionsDialog.pack();
      optionsDialog.setResizable(false);
      okButton.requestFocusInWindow();
    }

    optionsDialog.setVisible(true);
  }

  /**
   * Open a dialog to load a graph from a supported source and do the actual loading.
   */
  void openLoadDialog() {
    // Setup filechooser.
    JFileChooser fileChooser = new JFileChooser(".");
    fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);

    FileFilter defaultFilter = null;
    for (final Options.InFormat inFormat : Options.InFormat.values()) {
      if (inFormat.isDefaultGuiLoad()) {
        FileFilter filter = new FileFilter() {
          @Override
          public String getDescription() {
            return inFormat.getDescription();
          }

          @Override
          public boolean accept(File file) {
            return (file.isDirectory()
                || file.getAbsoluteFile().getName().toLowerCase().endsWith(inFormat.getFileExtension()));
          }
        };

        fileChooser.addChoosableFileFilter(filter);
        if (defaultFilter == null || inFormat == Options.InFormat.RSF) {
          defaultFilter = filter;
        }
      }
    }
    fileChooser.setFileFilter(defaultFilter);

    // Show filechooser.
    if (fileChooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
      String fileName = fileChooser.getSelectedFile().getAbsoluteFile().getAbsolutePath();
      options.inFormat = Options.getInFormatOfFile(fileName);
      options.getOption(OptionsEnum.inputName).set(fileName);

      try {
        // Load the choosen file.
        ccvisuController.processVisual(options, writer.getDisplay());
        this.updateWindow();
      } catch (IOException e) {
        JOptionPane.showMessageDialog(this, "Opening file failed: " + e.getMessage(),
            "Error", JOptionPane.ERROR_MESSAGE);
        e.printStackTrace();
      }
    }
  }

  /**
   * Open a dialog to save a graph in a supported format and do the actual saving.
   */
  void openSaveDialog() {
    JFileChooser fileChooser = new JFileChooser(".");
    fileChooser.setFileSelectionMode(JFileChooser.SAVE_DIALOG);

    for (final Options.OutFormat outFormat : Options.OutFormat.values()) {
      switch (outFormat) {
      case DISP:
      case NULL:
        break;
      default:
        fileChooser.addChoosableFileFilter(new FileFilter() {
          @Override
          public String getDescription() {
            return outFormat.getDescription();
          }

          @Override
          public boolean accept(File file) {
            return (file.isDirectory()
                || file.getAbsoluteFile().getName().toLowerCase().endsWith(outFormat.getFileExtension()));
          }

          @Override
          public String toString() {
            return outFormat.getFileExtension();
          }
        });
      }
    }

    if (fileChooser.showSaveDialog(this) == JFileChooser.APPROVE_OPTION) {
      String fileName = fileChooser.getSelectedFile().getAbsoluteFile().getAbsolutePath();
      OutFormat outFormat = Options.getOutFormatByExtension(fileChooser.getFileFilter().toString());

      try {
        ccvisuController.writeOutput(options.graph, fileName, outFormat, options);
        JOptionPane.showMessageDialog(this, "File successfully written!",
            "Done", JOptionPane.INFORMATION_MESSAGE);

      } catch (IOException e) {
        JOptionPane.showMessageDialog(this, "Writing file failed: " + e.getMessage(),
            "Error", JOptionPane.ERROR_MESSAGE);
        e.printStackTrace();
      }
    }
  }

  /**
   * Show an About-Dialog.
   */
  void openAboutDialog() {
    JOptionPane.showMessageDialog(this, options.versionMessage(), "About CCVisu",
        JOptionPane.INFORMATION_MESSAGE);
  }

  /**
   * Load next/previous layout from file,
   * to leaf through the directory file by file.
   * @param loadDirection  determines whether next or previous file
   *                       should be loaded.
   */
  void loadOtherFile(LoadDirection loadDirection) {
    final String fileName = options.getOption(OptionsEnum.inputName).getString();
    String path = ".";

    if (fileName.lastIndexOf(File.separator) != -1) {
      path = fileName.substring(0, fileName.lastIndexOf(File.separator));
    }

    String[] fileList = (new File(path)).list(new FilenameFilter() {
      @Override
      public boolean accept(File dir, String name) {
        return name.endsWith(".lay");
      }
    });

    if (fileList.length == 0) {
      System.err.println("No layout (.lay) file available in current directory.");
      return;
    }

    Arrays.sort(fileList);
    int fileCurrent = Arrays.binarySearch(fileList, new File(fileName).getName());

    if (fileCurrent < 0) { // File not found in current directory.
      fileCurrent = 0;

    } else if (loadDirection == LoadDirection.NEXT && (fileCurrent < fileList.length - 1)) {
      fileCurrent++;

    } else if (loadDirection == LoadDirection.PREV && (fileCurrent > 0)) {
      fileCurrent--;
    }

    // Load next layout.
    options.getOption(OptionsEnum.inputName).set(path + File.separator + fileList[fileCurrent]);
    loadFile(options.getOption(OptionsEnum.inputName).getString());
  }

  private void loadFile(String fileName) {
    BufferedReader reader = null;

    try {
      reader = new BufferedReader(new FileReader(fileName));
    } catch (Exception e) {
      System.err.println("Exception while opening file '" + fileName
          + "' for reading: ");
      System.err.println(e);
    }

    // Read layout from file.
    options.graph.reset();
    new ReaderDataLayoutLAY(reader, options.verbosity).read(options.graph);
    updateWindow();

    // Close the input file.
    try {
      reader.close();
    } catch (Exception e) {
      System.err.println("Exception while closing input file: ");
      System.err.println(e);
    }
  }

  /**
   * Writes layout to file using an implementation of class <code>WriterData</code>.
   * Call-back method, invoked from within ScreenDisplay.
   * @param fileName     Name of the output file to write the layout to.
   */
  public void writeFileLayout(String fileName) {
    try {
      PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(fileName)));
      Writer dataWriter = new WriterDataLayoutLAY(out, options.graph, options); // Default, also .lay.

      if (fileName.endsWith(".svg")) {
        dataWriter = new WriterDataLayoutSVG(out, options.graph, options);
      } else if (fileName.endsWith(".wrl")) {
        dataWriter = new WriterDataLayoutVRML(out, options.graph, options);
      }

      dataWriter.write();
      out.flush();
      out.close();
      options.infoCollector.addMessage("Wrote layout to output file '" + fileName + "'.");

    } catch (Exception e) {
      System.err.println("Exception while writing file '" + fileName + "': ");
      System.err.println(e);
    }
  }

  /**
   * This panel provides a search field to filter (and mark) vertices by a given name.
   */
  private class SearchPanel extends JPanel {

    private static final long serialVersionUID = -2212083044165367854L;

    private JTextField searchTextField   = new JTextField();

    public SearchPanel() {
      setLayout(new BorderLayout());
      setBorder(javax.swing.BorderFactory.createEmptyBorder(3, 0, 3, 0));
      add(new JLabel("Search for node: "), BorderLayout.WEST);
      add(searchTextField, BorderLayout.CENTER);

      searchTextField.addKeyListener(new KeyAdapter() {
        @Override
        public void keyPressed(KeyEvent e) {
          if (e.getKeyCode() == KeyEvent.VK_ENTER) {
            if (searchTextField.getText().isEmpty()) {
              writer.resetMarkedVertices();
            } else {
              writer.markVertices(".*" + searchTextField.getText() + ".*");
            }
            canvas.updateAndPaint();
          }
        }
      });
    }
  }

  private class StatusBar extends JPanel implements ProgressReceiver {

    private static final long serialVersionUID = -3044688812187823110L;

    private JProgressBar progressBar  = new JProgressBar();
    private JLabel       statusInfo   = new JLabel();

    public StatusBar() {
      setLayout(new BorderLayout(3, 3));
      setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
      add(statusInfo, BorderLayout.WEST);
      add(progressBar, BorderLayout.CENTER);
      setEnabled(false);
    }

    @Override
    public void processChange(final Object source, final int value, final int max,
        final String message) {

      SwingUtilities.invokeLater(new Runnable() {
        @Override
        public void run() {
          // update progress bar
          progressBar.setMaximum(max);
          progressBar.setValue(value);
          progressBar.setToolTipText(message);
          setVisible(progressBar.isEnabled());
          if (value == max) {
            progressBar.setValue(0);
            progressBar.setToolTipText(null);
          }

          // update label
          statusInfo.setText(message);
        }
      });
    }
  }
}
